/**
 * @license
 * Copyright 2021 Du Tian Wei
 * SPDX-License-Identifier: Apache-2.0
 */


OpenBlock.Struct = {};
OpenBlock.Struct.Blocks = {};
OpenBlock.Struct.baseTypes = [
    'Number',
    'Integer',
    'String',
    'Boolean',
    'FSM'
];
OpenBlock.addStruct = (src, opt) => {
    opt = Object.assign(OpenBlock.DataStructure.createFSMTemplate(), opt);
    if (!opt.name) {
        opt.name = OpenBlock.Utils.genName(OpenBlock.I18N.NEW_STRUCT_NAME, src.structs);
    }
    src.structs.push(opt);
    return opt;
};
OpenBlock.buildStructBlockly = (src, struct, domContainer) => {
    if (!domContainer) {
        throw new Error('domContainer is null');
    }
    if (src.structs.indexOf(struct) < 0) {
        throw new Error('Struct not in source');
    }
    domContainer.innerHTML = "";

    let xmlDom = OpenBlock.Struct.buildToolbox(src, struct);//Blockly.Xml.textToDom(OpenBlock.config.toolbox.struct);
    // OpenBlock._initStructToolbox(xml);
    let workspace = OpenBlock._buildBlockly(domContainer, xmlDom, struct.code, {
        _openblock_src: src,
        _openblock_struct: struct,
        _openblock_type: 'struct',
        _openblock_target: struct
    });
    let ret = {
        context: {
            src: src,
            struct: struct,
            type: "struct",
            workspace
        },
        value: struct,
        resize: function () {
            // workspace.resize();
            Blockly.svgResize(workspace);
        },
        dispose: function () {
            workspace.dispose();
        },
        saveCode: function () {
            struct.code = Blockly.Xml.domToText(Blockly.Xml.workspaceToDom(workspace));
        }
    };
    ret.context.workspace.saveCode = ret.saveCode;
    workspace.clearUndo();
    return ret;
};

OpenBlock.Struct.buildToolbox = function (src, struct) {
    let xmlDom = document.createElement('xml');

    xmlDom.appendChild(Blockly.Xml.textToDom('<block type="struct"></block>'));
    xmlDom.appendChild(Blockly.Xml.textToDom('<block type="struct_field"></block>'));
    xmlDom.appendChild(Blockly.Xml.textToDom('<block type="struct_base_type"></block>'));
    xmlDom.appendChild(Blockly.Xml.textToDom('<block type="struct_structs"></block>'));
    xmlDom.appendChild(Blockly.Xml.textToDom('<block type="struct_list"></block>'));
    xmlDom.appendChild(Blockly.Xml.textToDom('<block type="struct_string_map"></block>'));
    xmlDom.appendChild(Blockly.Xml.textToDom('<block type="struct_integer_map"></block>'));

    return xmlDom;
};

// ++++++++ blocks ++++++
Blockly.Blocks["struct"] = {
    init: function () {
        this.jsonInit({
            "message0": "数据类型 %1 %2",
            "args0": [{
                "type": "field_input",
                "name": "NAME",
            }, {
                "type": "input_statement",
                "name": "FIELDS",
                "check": "field"
            }
            ],
            "style": "struct_blocks"
        });
    }
};
Blockly.Blocks["struct_field"] = {
    init: function () {
        this.jsonInit({
            "message0": "名称 %1 类型 %2",
            "args0": [{
                "type": "field_input",
                "name": "NAME",
            }, {
                "type": "input_value",
                "name": "TYPE",
                "check": "DataType"
            }
            ],
            "style": "struct_blocks",
            "nextStatement": "field",
            "previousStatement": "field"
        });
    }
};
Blockly.Blocks["struct_list"] = {
    init: function () {
        this.jsonInit({
            "message0": "列表 元素类型 %1",
            "args0": [{
                "type": "input_value",
                "name": "TYPE",
                "check": "DataType"
            }
            ],
            "style": "struct_blocks",
            "output": "DataType"
        });
    },
    toCode() {
        let elementType = this.getInput('TYPE');
        if (elementType.connection.isConnected()) {
            let typeBlock = elementType.connection.targetBlock();
            let typeName = typeBlock.toCode();
            return 'list<' + typeName + '>';
        }
    }
};
Blockly.Blocks["struct_string_map"] = {
    init: function () {
        this.jsonInit({
            "message0": "字符串映射 元素类型 %1",
            "args0": [{
                "type": "input_value",
                "name": "TYPE",
                "check": "DataType"
            }
            ],
            "style": "struct_blocks",
            "output": "DataType"
        });
    },
    toCode() {
        let elementType = this.getInput('TYPE');
        if (elementType.connection.isConnected()) {
            let typeBlock = elementType.connection.targetBlock();
            let typeName = typeBlock.toCode();
            return 'string_map<' + typeName + '>';
        }
    }
};
Blockly.Blocks["struct_integer_map"] = {
    init: function () {
        this.jsonInit({
            "message0": "整数映射 元素类型 %1",
            "args0": [{
                "type": "input_value",
                "name": "TYPE",
                "check": "DataType"
            }
            ],
            "style": "struct_blocks",
            "output": "DataType"
        });
    },
    toCode() {
        let elementType = this.getInput('TYPE');
        if (elementType.connection.isConnected()) {
            let typeBlock = elementType.connection.targetBlock();
            let typeName = typeBlock.toCode();
            return 'integer_map<' + typeName + '>';
        }
    }
};
Blockly.Blocks["struct_base_type"] = {
    init: function () {
        this.jsonInit({
            "message0": "基本类型 %1",
            "args0": [{
                "type": "field_dropdown_base_type",
                "name": "TYPE",
            }
            ],
            "style": "struct_blocks",
            "output": "DataType"
        });
    },
    toCode() {
        return this.getFieldValue('TYPE');
    }
};
Blockly.Blocks["struct_new"] = {
    init() {
        this.jsonInit({
            "message0": "创建 %1 类型数据",
            "args0": [{
                "type": "field_dropdown_structs",
                "name": "TYPE",
            }
            ],
            "style": "struct_blocks",
            "output": null
        });
    },
    onchange(e) {
        let type = this.getFieldValue('TYPE');
        this.outputConnection.setCheck(type);
    }
};
Blockly.Blocks["struct_structs"] = {
    init: function () {
        this.jsonInit({
            "message0": "数据结构 %1",
            "args0": [{
                "type": "field_dropdown_structs",
                "name": "TYPE",
            }
            ],
            "style": "struct_blocks",
            "output": "DataType"
        });
    },
    toCode() {
        return this.getFieldValue('TYPE');
    }
};

Blockly.Blocks['struct_load_from_dataset'] = {
    init: function () {
        this.jsonInit({
            "message0": "从数据集中加载 ID为 %1 的 %2",
            "args0": [{
                "type": "input_value",
                "name": "ID",
                "check": ["Integer"]
            }, {
                "type": "field_dropdown_structs",
                "name": "TYPE",
            }
            ],
            "style": "struct_blocks",
            "output": null
        });
        this.onchange = function (e) {
            if ((e.recordUndo) && e.blockId === this.id || (e instanceof Blockly.Events.Create)) {
                let type = this.getFieldValue('TYPE');
                this.outputConnection.setCheck(type);
            }
        }
    }
};
Blockly.Blocks["struct_get_field"] = {
    init() {
        this.jsonInit({
            "message0": "从 数据 %1 中获取 %2 的值",
            "args0": [{
                "type": "input_value",
                "name": "DATA"
            }, {
                "type": "struct_get_field_dropdown",
                "name": "FIELD",
            }],
            "style": "struct_blocks",
            "output": null
        });
    },
    onchange(e) {
        if ((e.recordUndo) && e.blockId === this.id || (e instanceof Blockly.Events.Create)) {
            let field = this.getField('FIELD');
            let v = field.getValue();
            field.doValueUpdate_(v);
        }
    }
};
Blockly.Blocks["struct_set_field"] = {
    init() {
        this.jsonInit({
            "message0": "设置 数据 %1 中 %2 的值为 %3",
            "args0": [{
                "type": "input_value",
                "name": "DATA"
            }, {
                "type": "struct_get_field_dropdown",
                "name": "FIELD",
            }, {
                "type": "input_value",
                "name": "VALUE"
            }],
            "style": "struct_blocks",
            "nextStatement": "inst",
            "previousStatement": "inst"
        });
    },
    onchange(e) {
        if ((e.recordUndo) && e.blockId === this.id || (e instanceof Blockly.Events.Create)) {
            let field = this.getField('FIELD');
            let v = field.getValue();
            field.doValueUpdate_(v);
        }
    }
};
Blockly.Blocks['struct_sort_list_field'] = {
    init() {
        this.jsonInit({
            "message0": "将 数据 %1 的列表 %2 以 %3 排序",
            "args0": [{
                "type": "input_value",
                "name": "DATA"
            }, {
                "type": "struct_get_field_dropdown",
                "name": "FIELD",
            }, {
                "type": "field_dropdown",
                "name": "SORT",
                "options": [
                    ["正序", "A"],
                    ["倒叙", "D"]
                ]
            }],
            "style": "struct_blocks",
            "previousStatement": "inst",
            "nextStatement": "inst",
        });
    }
};
Blockly.Blocks['struct_get_most_from_list_field'] = {
    init() {
        this.jsonInit({
            "message0": "获取 数据 %1 的列表 %2 中 %3",
            "args0": [{
                "type": "input_value",
                "name": "DATA"
            }, {
                "type": "struct_get_field_dropdown",
                "name": "FIELD",
            }, {
                "type": "field_dropdown",
                "name": "SORT",
                "options": [
                    ["最小", "S"],
                    ["最大", "L"]
                ]
            }],
            "style": "struct_blocks",
            "output": null
        });
    }
};
Blockly.Blocks['struct_get_map_value_of_key'] = {
    init() {
        this.jsonInit({
            "message0": "获取 字符串映射 %1 中 %2 对应的值",
            "args0": [{
                "type": "input_value",
                "name": "MAP"
            }, {
                "type": "input_value",
                "name": "KEY",
                "check": "String"
            }],
            "style": "struct_blocks",
            "output": null
        });
    }
};
// +++++++++++
OpenBlock.Struct.Blocks.StructGetFieldDropdown = function (opt_config) {
    // Call parent's constructor.
    OpenBlock.Struct.Blocks.StructGetFieldDropdown.superClass_.constructor.call(this, OpenBlock.Struct.Blocks.StructGetFieldDropdown.menuGenerator);
}
Blockly.utils.object.inherits(OpenBlock.Struct.Blocks.StructGetFieldDropdown, Blockly.FieldDropdown);
OpenBlock.Struct.Blocks.StructGetFieldDropdown.menuGenerator = function () {
    let blk = this.getSourceBlock();
    if (!blk) {
        return [['', '']];
    }

    let datablk = blk.getInputTargetBlock('DATA');
    if (!datablk) {
        return [['', '']];
    }
    let check = datablk.outputConnection.getCheck();
    if (!check) {
        return [['', '']];
    }
    let typeName = check[0];
    let stDef = OpenBlock.BlocklyParser.getStructDefByName(typeName);
    if (!stDef) {
        return [['', '']];
    }
    let menu = [];
    stDef.fields.forEach(fd => {
        menu.push([fd.name, stDef.fullname + ':' + fd.name + '/' + fd.type.name]);
    });
    if (menu.length === 0) {
        menu.push(['', '']);
    }
    return menu;
};
OpenBlock.Struct.Blocks.StructGetFieldDropdown.prototype.doClassValidation_ = function (a) {
    return a;
};
OpenBlock.Struct.Blocks.StructGetFieldDropdown.prototype.doValueUpdate_ = function (newValue) {
    OpenBlock.Struct.Blocks.StructGetFieldDropdown.superClass_.doValueUpdate_.call(this, newValue);
    var options = this.getOptions(true);
    for (var i = 0, option; (option = options[i]); i++) {
        if (option[1] === this.value_) {
            this.selectedOption_ = option;
            let blk = this.getSourceBlock();
            if (blk) {

                let datablk = blk.getInputTargetBlock('DATA');
                if (!datablk) {
                    return;
                }
                let check = datablk.outputConnection.getCheck();
                let typeName = check[0];
                let stDef = OpenBlock.BlocklyParser.getStructDefByName(typeName);
                if (!stDef) {
                    return;
                }
                let start = newValue.indexOf(":");
                if (start < 0) {
                    return;
                }
                let end = newValue.indexOf("/");
                if (end < 0) {
                    return;
                }
                let fieldName = newValue.substr(start + 1, end - start - 1);
                let fieldType = null;
                for (let k in stDef.fields) {
                    let f = stDef.fields[k];
                    if (f.name === fieldName) {
                        fieldType = f.type.name;
                        break;
                    }
                }
                blk.setWarningText();
                if (blk.outputConnection) {
                    blk.outputConnection.setCheck(fieldType)
                }
                let valueInput = blk.getInput('VALUE');
                if (valueInput) {
                    valueInput.setCheck(fieldType);
                }
            }
            return;
        }
    }
    this.selectedOption_ = [newValue, newValue];
    let blk = this.getSourceBlock();
    if (blk) {
        blk.setWarningText('找不到字段 ' + newValue);
    }
};
OpenBlock.Struct.Blocks.StructGetFieldDropdown.prototype.toXml = function (fieldElement) {
    fieldElement.textContent = this.getValue();
    return fieldElement;
};
OpenBlock.Struct.Blocks.StructGetFieldDropdown.prototype.fromXml = function (fieldElement) {
    this.setValue(fieldElement.textContent);
};
OpenBlock.Struct.Blocks.StructGetFieldDropdown.prototype.setValue = function (newValue) {
    Blockly.Field.prototype.setValue.call(this, newValue);
}
OpenBlock.Struct.Blocks.StructGetFieldDropdown.prototype.getOptions = function (opt_useCache) {
    return OpenBlock.Struct.Blocks.StructGetFieldDropdown.menuGenerator.call(this);
}
OpenBlock.Struct.Blocks.StructGetFieldDropdown.fromJson = function (options) {
    return new OpenBlock.Struct.Blocks.StructGetFieldDropdown(options);
};

Blockly.fieldRegistry.register('struct_get_field_dropdown', OpenBlock.Struct.Blocks.StructGetFieldDropdown);


// -----------
OpenBlock.Struct.Blocks.BaseTypeFieldDropdown = function (opt_config) {
    // Call parent's constructor.
    OpenBlock.Struct.Blocks.BaseTypeFieldDropdown.superClass_.constructor.call(this, OpenBlock.Struct.Blocks.BaseTypeFieldDropdown.menuGenerator);
}
Blockly.utils.object.inherits(OpenBlock.Struct.Blocks.BaseTypeFieldDropdown, Blockly.FieldDropdown);
OpenBlock.Struct.Blocks.BaseTypeFieldDropdown.menuGenerator = function () {
    return OpenBlock.I18N.PRIMARY_TYPES;
};
OpenBlock.Struct.Blocks.BaseTypeFieldDropdown.prototype.doClassValidation_ = function (a) {
    return a;
};

OpenBlock.Struct.Blocks.BaseTypeFieldDropdown.prototype.doValueUpdate_ = function (newValue) {
    OpenBlock.Struct.Blocks.BaseTypeFieldDropdown.superClass_.doValueUpdate_.call(this, newValue);
    var options = this.getOptions(true);
    for (var i = 0, option; (option = options[i]); i++) {
        if (option[1] === this.value_) {
            this.selectedOption_ = option;
            let blk = this.getSourceBlock();
            if (blk) {
                blk.setWarningText();
            }
            return;
        }
    }
    this.selectedOption_ = [newValue, newValue];
    let blk = this.getSourceBlock();
    if (blk) {
        blk.setWarningText('找不到变量 ' + newValue);
    }
};
OpenBlock.Struct.Blocks.BaseTypeFieldDropdown.prototype.toXml = function (fieldElement) {
    fieldElement.textContent = this.getValue();
    return fieldElement;
};
OpenBlock.Struct.Blocks.BaseTypeFieldDropdown.prototype.fromXml = function (fieldElement) {
    this.setValue(fieldElement.textContent);
};
OpenBlock.Struct.Blocks.BaseTypeFieldDropdown.prototype.setValue = function (newValue) {
    Blockly.Field.prototype.setValue.call(this, newValue);
}
OpenBlock.Struct.Blocks.BaseTypeFieldDropdown.prototype.getOptions = function (opt_useCache) {
    return OpenBlock.Struct.Blocks.BaseTypeFieldDropdown.menuGenerator.call(this);
}
OpenBlock.Struct.Blocks.BaseTypeFieldDropdown.fromJson = function (options) {
    return new OpenBlock.Struct.Blocks.BaseTypeFieldDropdown(options);
};

Blockly.fieldRegistry.register('field_dropdown_base_type', OpenBlock.Struct.Blocks.BaseTypeFieldDropdown);



OpenBlock.Struct.Blocks.StructsFieldDropdown = function (opt_config) {
    // Call parent's constructor.
    OpenBlock.Struct.Blocks.StructsFieldDropdown.superClass_.constructor.call(this, OpenBlock.Struct.Blocks.StructsFieldDropdown.menuGenerator);
}
Blockly.utils.object.inherits(OpenBlock.Struct.Blocks.StructsFieldDropdown, Blockly.FieldDropdown);
OpenBlock.Struct.Blocks.StructsFieldDropdown.menuGenerator = function () {
    let blk = this.getSourceBlock();
    if (!blk) {
        return [['', '']];
    } else {
        let env = (blk.workspace._openblock_env || blk.workspace.targetWorkspace._openblock_env);
        let src = env._openblock_src;
        let struct = env._openblock_struct;
        let structs = OpenBlock.getRelatedStructs(src);
        let menu = [];
        Object.keys(structs).forEach(fullname => {
            menu.push([fullname, fullname]);
        });
        return menu;
    }
};
OpenBlock.Struct.Blocks.StructsFieldDropdown.prototype.doClassValidation_ = function (a) {
    return a;
};
OpenBlock.Struct.Blocks.StructsFieldDropdown.prototype.doValueUpdate_ = function (newValue) {
    OpenBlock.Struct.Blocks.StructsFieldDropdown.superClass_.doValueUpdate_.call(this, newValue);
    var options = this.getOptions(true);
    for (var i = 0, option; (option = options[i]); i++) {
        if (option[1] === this.value_) {
            this.selectedOption_ = option;
            let blk = this.getSourceBlock();
            if (blk) {
                blk.setWarningText();
            }
            return;
        }
    }
    this.selectedOption_ = [newValue, newValue];
    let blk = this.getSourceBlock();
    if (blk) {
        blk.setWarningText('找不到类型 ' + newValue);
    }
};
OpenBlock.Struct.Blocks.StructsFieldDropdown.prototype.toXml = function (fieldElement) {
    fieldElement.textContent = this.getValue();
    return fieldElement;
};
OpenBlock.Struct.Blocks.StructsFieldDropdown.prototype.fromXml = function (fieldElement) {
    this.setValue(fieldElement.textContent);
};
OpenBlock.Struct.Blocks.StructsFieldDropdown.prototype.setValue = function (newValue) {
    Blockly.Field.prototype.setValue.call(this, newValue);
}
OpenBlock.Struct.Blocks.StructsFieldDropdown.prototype.getOptions = function (opt_useCache) {
    return OpenBlock.Struct.Blocks.StructsFieldDropdown.menuGenerator.call(this);
}
OpenBlock.Struct.Blocks.StructsFieldDropdown.fromJson = function (options) {
    return new OpenBlock.Struct.Blocks.StructsFieldDropdown(options);
};

Blockly.fieldRegistry.register('field_dropdown_structs', OpenBlock.Struct.Blocks.StructsFieldDropdown);
// ---------- blocks -------

OpenBlock.getRelatedStructs = (src) => {
    let relatedSrc = OpenBlock.getRelatedSrcOrLib(src);

    let structMap = {};
    relatedSrc.forEach(rSrc => {
        // structArr = structArr.concat(rSrc.structs);
        rSrc.__analyzed.structs.forEach(st => {
            let fullname = rSrc.name + "." + st.name;
            structMap[fullname] = st;
        });
    });
    return structMap;
};
